﻿using SS.Common.Logging;
using SS.Common.Utilities;

namespace SS.WPF.Framework.Restriction
{
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Xml;
    using System.Xml.Schema;

    /// <summary>
    /// Build collection of Restriction from XSD file
    /// <para>
    /// Key: name of property
    /// </para>
    /// <para>
    /// Value: instance of IRestriction, each instance will be assigned to ValidationRules to validate data bound
    /// </para>
    /// </summary>
    public class RestrictionDictionary : Dictionary<string, IRestriction>, IRestrictionDictionary
    {
        private static readonly ILogger Logger = LoggerManager.GetLogger(typeof(RestrictionDictionary).Name);

        private XmlSchemaSet _xsdSet;

        public void Initialize(string embeddedXsdResourceName)
        {
            this._xsdSet = new XmlSchemaSet();
            // Copy embedded configure file                    
            var callingAssembly = Assembly.GetCallingAssembly();
            var stream = callingAssembly.GetManifestResourceStream(embeddedXsdResourceName);
            if (stream != null)
            {
                using (stream)
                {
                    var xsd = this._xsdSet.Add(null, XmlReader.Create(stream));
                    this._xsdSet.Add(xsd);
                }
            }
            else
            {
                Logger.FatalFormat("Failed to intialize Restriction from Embeded Schemas [{0}]", embeddedXsdResourceName);
            }

            _xsdSet.Compile();

            var collection = this._xsdSet.Schemas();
            foreach (XmlSchema item in collection)
            {
                foreach (DictionaryEntry tbl in item.SchemaTypes)
                {
                    if (tbl.Value is XmlSchemaSimpleType)
                    {
                        var type = (XmlSchemaSimpleType)tbl.Value;
                        var baseType = type.BaseXmlSchemaType;
                        var restriction = type.Content as XmlSchemaSimpleTypeRestriction;
                        if (restriction != null)
                        {
                            var enums = restriction.Facets.OfType<XmlSchemaEnumerationFacet>().Select(x => x.Value).ToArray();
                            var typeName = tbl.Key.ToString();

                            if (enums.Length > 0)
                            {
                                int dummy;
                                if (baseType.Datatype.ValueType == typeof(int))// int.TryParse(enums[0], out dummy)
                                {
                                    var dict = enums.ToDictionary(x => x, x => int.Parse(x));
                                    var propertyType = DynamicTypeHelper.CreateEnum(typeName, dict);
                                    this[typeName] = new IntegerEnumRestriction() { EnumType = propertyType };
                                }
                                else if (baseType.Datatype.ValueType == typeof(float)
                                    || baseType.Datatype.ValueType == typeof(double))
                                {
                                    var propertyType = DynamicTypeHelper.CreateEnum(typeName, enums);
                                    this[typeName] = new DoubleEnumRestriction() { EnumType = propertyType };
                                }
                                else
                                {
                                    var propertyType = DynamicTypeHelper.CreateEnum(typeName, enums);
                                    this[typeName] = new StringEnumRestriction() { EnumType = propertyType };
                                }

                                continue;
                            }
                            else
                            {
                                var mins = restriction.Facets.OfType<XmlSchemaMinInclusiveFacet>().Select(x => x.Value).ToArray();
                                var maxs = restriction.Facets.OfType<XmlSchemaMaxInclusiveFacet>().Select(x => x.Value).ToArray();
                                if (baseType.Datatype.ValueType == typeof(int))// int.TryParse(enums[0], out dummy)
                                {
                                    if (mins.Length > 0 || maxs.Length > 0)
                                    {
                                        var rangeRestrition = new RangeRestriction();
                                        if (mins.Length > 0)
                                        {
                                            rangeRestrition.Minimum = int.Parse(mins[0]);
                                        }

                                        if (maxs.Length > 0)
                                        {
                                            rangeRestrition.Maximum = ulong.Parse(maxs[0]);
                                        }

                                        this[typeName] = rangeRestrition;
                                        continue;
                                    }
                                }

                                if (baseType.Datatype.ValueType == typeof(float)
                                       || baseType.Datatype.ValueType == typeof(double))
                                {
                                    var rangeRestrition = new DoubleRangeRestriction();
                                    if (mins.Length > 0)
                                    {
                                        rangeRestrition.Minimum = double.Parse(mins[0]);
                                    }

                                    if (maxs.Length > 0)
                                    {
                                        rangeRestrition.Maximum = double.Parse(maxs[0]);
                                    }

                                    this[typeName] = rangeRestrition;
                                    continue;
                                }

                                if (baseType.Datatype.ValueType == typeof(uint)
                                    || baseType.Datatype.ValueType == typeof(ulong))
                                {
                                    var rangeRestrition = new UnsignedRangeRestriction();
                                    if (mins.Length > 0)
                                    {
                                        rangeRestrition.Minimum = ulong.Parse(mins[0]);
                                    }

                                    if (maxs.Length > 0)
                                    {
                                        rangeRestrition.Maximum = ulong.Parse(maxs[0]);
                                    }

                                    this[typeName] = rangeRestrition;
                                    continue;
                                }

                                IList<IRestriction> restrictions = new List<IRestriction>();
                                var patterns = restriction.Facets.OfType<XmlSchemaPatternFacet>().Select(x => x.Value).ToArray();
                                if (patterns.Length > 0)
                                {
                                    var patternRestrition = new RegexRestriction() 
                                    {
                                        RegexPattern = string.Format("^{0}$", patterns[0])
                                    };
                                    this[typeName] = patternRestrition;
                                    restrictions.Add(patternRestrition);
                                    // continue;
                                }


                                var maxLengths = restriction.Facets.OfType<XmlSchemaMaxLengthFacet>().Select(x => x.Value).ToArray();
                                var minLengths = restriction.Facets.OfType<XmlSchemaMinLengthFacet>().Select(x => x.Value).ToArray();
                                int? maxLength = null;
                                if (maxLengths.Length > 0)
                                {
                                    maxLength = int.Parse(maxLengths[0]);
                                }

                                int? minLength = null;
                                if (minLengths.Length > 0)
                                {
                                    minLength = int.Parse(minLengths[0]);
                                }

                                if (maxLength != null || minLength != null)
                                {
                                    var patternRestrition = new MaxLengthRestriction()
                                    {
                                        MaxLength = maxLength,
                                        MinLength = minLength
                                    };

                                    this[typeName] = patternRestrition;
                                    restrictions.Add(patternRestrition);
                                }

                                if (restrictions.Count > 1)
                                {
                                    this[typeName] = new CompositeRestriction() { Restrictions = restrictions };
                                    continue;
                                }
                                else if (restrictions.Count == 1)
                                {
                                    continue;
                                }
                            }

                            foreach (var facet in restriction.Facets)
                            {
                                Logger.ErrorFormat("Unsupport Restriction {0}", facet);
                            }
                        }
                        else
                        {
                            Logger.Debug("Unexpected flow");
                        }
                    }
                }
            }
        }

        public XmlSchemaType GetSchemas(string typeName)
        {
            var collection = this._xsdSet.Schemas();
            foreach (XmlSchema item in collection)
            {
                foreach (DictionaryEntry tbl in item.SchemaTypes)
                {
                    if (typeName.Equals(tbl.Key.ToString()))
                    {
                        return tbl.Value as XmlSchemaType;
                    }
                }
            }

            return null;
        }

        public XmlSchemaSimpleType GetSimpleTypeSchemas(string typeName)
        {
            return GetSchemas(typeName) as XmlSchemaSimpleType;
        }

        public XmlSchemaSimpleTypeRestriction GetSimpleTypeRestriction(string typeName)
        {
            var type = GetSchemas(typeName) as XmlSchemaSimpleType;
            if (type != null)
            {
                return type.Content as XmlSchemaSimpleTypeRestriction;
            }

            return null;
        }

        public IEnumerable<XmlSchemaEnumerationFacet> GetXmlSchemaEnumerationFacets(string typeName)
        {
            var restriction = GetSimpleTypeRestriction(typeName);
            if (restriction != null)
            {
                return restriction.Facets.OfType<XmlSchemaEnumerationFacet>();
            }

            return null;
        }
    }
}